Router = require "koa-router"
fs = require "fs/promises"
db = require "../db"
Bot = require "../bot"
_ = require "lodash"
monk = require "monk"
axios = require "axios"
dayjs = require "dayjs"
crypto = require "crypto"
util = require "util"
qs = require "querystring"
utils = require "../utils"
router = new Router prefix: "/client"

{ FUWUHAOID, FUWUHAOSECRET } = process.env

getOpenid = (ctx, next) ->
  if ctx.request.query.openid
    ctx.session.openid = ctx.request.query.openid

  if ctx.session.openid
    ctx.state.openid = ctx.session.openid
    return await next()

  if ctx.request.query.code
    code = ctx.request.query.code

    res =
      await axios.get(
        "https://api.weixin.qq.com/sns/oauth2/access_token?appid=#{FUWUHAOID}&code=#{code}&secret=#{FUWUHAOSECRET}&grant_type=authorization_code"
      )
    if res.data?.errcode
      return (ctx.body = res.data)
    { data: { access_token, openid } } = res
    ctx.session.openid = openid

    return ctx.redirect ctx.path

  url = encodeURIComponent process.env.DOMAIN + ctx.request.path

  url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{FUWUHAOID}&redirect_uri=#{url}&response_type=code&scope=snsapi_base"
  ctx.redirect url

weixinBridge = (ctx, next) ->
  timestamp = Math.round new Date().getTime() / 1000
  noncestr = timestamp + "t"
  jsapi_ticket = await utils.getTicket()
  hash = crypto.createHash "sha1"
  str = qs.stringify(
    jsapi_ticket: jsapi_ticket
    noncestr: noncestr
    timestamp: timestamp
    url: process.env.DOMAIN + ctx.path
  )
  str = qs.unescape str
  hash.update str

  sign = hash.digest().toString "hex"
  console.log sign
  ctx.state.jsapi =
    noncestr: noncestr
    timestamp: timestamp
    sign: sign

  await next()

checkClient = (ctx, next) ->
  client = await db.CLIENT.findOne openid: ctx.state.openid
  ctx.state.client = client
  unless client
    return await ctx.render "client/403"
  await next()

module.exports =
  getOpenid: getOpenid
  weixinBridge: weixinBridge
  checkClient: checkClient

  bind: (ctx) ->
    { contactid } = ctx.params
    { openid } = ctx.session
    contact = await db.CONTACT.findOne id: contactid

    if contact?.openid is openid
      return await ctx.redirect "/client/redirectHome"

    if contact?.openid
      return await ctx.redirect "/client/403"

    contact2 = await db.CONTACT.findOne openid: openid
    if contact2
      return await ctx.redirect "/client/redirectHome"

    client =
      await db.CLIENT.findOneAndUpdate
        contactid: contactid
        openid: openid
      ,
        $set:
          activeTime: new Date()
      ,
        upsert: true
    contact = await db.CONTACT.findOneAndUpdate { id: contactid }, $set: openid: openid

    ctx.state.contact = contact
    return await ctx.redirect "/client/redirectHome"

  room: (ctx) ->
    roomid = ctx.params.roomid

  redirectHome: (ctx) ->
    # 前端跳转首页 避免浏览器缓存302
    console.log "redirect home"
    await ctx.render "client/redirectHome"

  home: (ctx) ->
    openid = ctx.state.openid
    contact = await db.CONTACT.findOne openid: openid

    ctx.state.client =
      await db.CLIENT.findOne
        openid: openid
        contactid: contact.id

    ctx.state.shorturl = await utils.shortlink process.env.DOMAIN + "/user/client/" + ctx.state.client._id
    await ctx.render "client/home", contact: contact

  home2: (ctx) ->
    openid = ctx.state.openid
    contact = await db.CONTACT.findOne openid: openid

    ctx.state.client =
      await db.CLIENT.findOne
        openid: openid
        contactid: contact.id

    ctx.state.shorturl = await utils.shortlink process.env.DOMAIN + "/user/client/" + ctx.state.client._id
    await ctx.render "client/home2", contact: contact

  usersPage: (ctx) ->
    await ctx.render "client/users"

  getUsers: (ctx) ->
    users = await db.POINT_DATA.find {clientid: ctx.state.client._id}, {sort:{point: -1}}
    for user in users
      user.userinfo = await db.USER.findOne contactid: user.contactid
    ctx.body = status: true, users: users

  userPage: (ctx) ->
    contactid = ctx.params.contactid
    await ctx.render "client/user", contactid: contactid

  getUserLogs: (ctx) ->
    contactid = ctx.params.contactid

    logs =
      await db.POINT_LOG.find
        contactid: contactid
        clientid: ctx.state.client._id
      ,
        sort: _id: -1

    userinfo = await db.USER.findOne contactid: contactid
    user =
      await db.POINT_DATA.findOne
        clientid: ctx.state.client._id
        contactid: contactid

    for log in logs
      log.createTime = dayjs(log._id.getTimestamp()).format "YYYY-MM-DD HH:mm"
    ctx.body =
      status: true
      logs: logs
      userinfo: userinfo
      point: user?.point || 0

  clearPoints: (ctx) ->
    { clientid } = ctx.request.body
    unless ctx.state.client._id.toString() is clientid
      return (ctx.body = status: false, msg: "无权操作")
    await db.POINT_DATA.remove clientid: monk.id clientid
    await db.POINT_LOG.remove clientid: monk.id clientid
    ctx.body = status: true
    # await
  chargesPage: (ctx) ->
    await ctx.render "client/charges"

  getCharges: (ctx) ->
    charges =
      await db.ORDER.find
        "client._id": ctx.state.client._id
        completed: true

    for charge in charges
      charge.createTime = dayjs(charge._id.getTimestamp()).format "YYYY-MM-DD HH:mm"
    ctx.body = status: true, charges: charges

  itemPage: (ctx) ->
    itemid = ctx.params.itemid or ""
    await ctx.render "client/item", itemid: itemid

  drawPage: (ctx) ->
    drawid = ctx.params.drawid or ""
    draw = null
    if drawid
      draw = await db.POINT_DRAW.findOne drawid
      draw.opentime = dayjs(draw.opentime).format "YYYY-MM-DDTHH:MM"
    await ctx.render "/client/draw", { draw, drawid }

  pointOrdersPage: (ctx) ->
    await ctx.render "client/pointOrders"

  getPointOrders: (ctx) ->
    pointOrders =
      await db.POINT_ORDER.find
        clientid: ctx.state.client._id
        $or: [{ "item.type": "red", "payment.result_code": "SUCCESS" }, { "item.type": "real" }, {"item.type": 'code'}]
      ,
        sort: _id: -1
    for order in pointOrders
      order.createTime = dayjs(order._id.getTimestamp()).format "YYYY-MM-DD HH:mm"
      order.userinfo = await db.USER.findOne order.userid
    ctx.body = status: true, pointOrders: pointOrders

  getClient: (ctx) ->
    openid = ctx.state.openid
    contact = await db.CONTACT.findOne openid: openid
    # contact = Bot.get().Contact.find db_contact.id
    # console.log contact
    client =
      await db.CLIENT.findOne
        openid: openid
        contactid: contact.id
    vip_num =
      await db.ROOM.count
        inviterid: client.contactid
        expires:
          $gt: new Date()
    ctx.body =
      status: true
      client:
        _id: client._id
        # avatar: contact.avatar
        name: contact.name
        red_money: if client.red_fee then client.red_fee / 100 else 0
        vip_num: vip_num
        home: client.home
        tryed: client.tryed

  getRooms: (ctx) ->
    client = ctx.state.client
    rooms = await db.ROOM.find inviterid: client.contactid
    for room in rooms
      room.pointRule = await db.POINT_RULE.findOne clientid: client._id, roomid: room.id
      room.vip = room.expires > new Date()
      room.expiresDate = dayjs(room.expires).format "YYYY-MM-DD"
    ctx.body =
      status: true
      rooms: rooms

  getActiveRooms: (ctx) ->
    client = ctx.state.client
    rooms =
      await db.ROOM.find
        inviterid: client.contactid
        expires: $gt: new Date()
    ctx.body =
      status: true
      rooms: rooms

  removeRoom: (ctx) ->
    { room } = ctx.request.body
    r = await db.ROOM.findOne room._id
    if r
      await db.REMOVED.insert data: r
      await db.ROOM.remove _id: r._id
    ctx.body = status: true

  chargeRoom: (ctx) ->
    { room } = ctx.request.body

    openid = ctx.state.openid
    contact = await db.CONTACT.findOne openid: openid
    bot_model = await db.BOT.findOne id: contact.botid

    client = ctx.state.client
    order =
      await db.ORDER.insert
        client: client
        room: room
        openid: openid
        total_fee: Math.round process.env.MONTHCHARGE * 100
    # console.log(room)

    paybody =
      body: "群功能开通"
      out_trade_no: order._id.toString()
      total_fee: order.total_fee
      spbill_create_ip: ctx.ip
      openid: client.openid
      notify_url: process.env.DOMAIN + "/client/room/charge/callback"
      trade_type: "JSAPI"

    ret = await util.promisify(utils.wepay.getBrandWCPayRequestParams.bind utils.wepay) paybody

    ctx.body = ret

  redeemRoom: (ctx) ->
    { code, room } = ctx.request.body
    redeem = await db.REDEEM.findOne code: code.trim()
    unless redeem
      return (ctx.body = status: false, msg: "兑换码不存在")
    if redeem.roomid
      return (ctx.body = status: false, msg: "兑换码已使用")

    room = await db.ROOM.findOne _id: monk.id room._id
    await db.REDEEM.findOneAndUpdate
      _id: redeem._id
    ,
      $set:
        roomid: room.id
        topic: room.topic
        redeemTime: new Date()
    redeemDays = redeem.days or 30
    expires = dayjs()
      .add redeemDays, "day"
      .toDate()
    if room.expires and room.expires > new Date()
      expires = dayjs room.expires
        .add redeemDays, "day"
        .toDate()
    await db.ROOM.findOneAndUpdate
      _id: monk.id room._id
    ,
      $set:
        expires: expires
    ctx.body = status: true

  tryRoom: (ctx) ->
    { room } = ctx.request.body

    openid = ctx.state.openid
    contact = await db.CONTACT.findOne openid: openid
    bot_model = await db.BOT.findOne id: contact.botid

    client = ctx.state.client
    if client.tryed
      return (ctx.body = status: false, msg: "已试用过")

    expires = dayjs()
      .add process.env.TRY, "day"
      .toDate()
    await db.ROOM.findOneAndUpdate
      _id: monk.id room._id
    ,
      $set:
        expires: expires

    await db.CLIENT.findOneAndUpdate
      _id: client._id
    ,
      $set:
        tryed: true
    ctx.body = status: true
  chargeRoomCallback: (ctx) ->
    { xml } = await utils.xml2js ctx.request.body
    order = await db.ORDER.findOne xml.out_trade_no
    console.log xml
    successXML = """<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>"""

    if order.completed
      return (ctx.body = successXML)

    expires = dayjs()
      .add process.env.CHARGEDAYS, "day"
      .toDate()
    room = await db.ROOM.findOne order.room._id
    if room.expires and room.expires > new Date()
      expires = dayjs room.expires
        .add process.env.CHARGEDAYS, "day"
        .toDate()

    await db.ROOM.findOneAndUpdate
      _id: order.room._id
    ,
      $set:
        expires: expires

    await db.ORDER.findOneAndUpdate
      _id: monk.id xml.out_trade_no
    ,
      $set:
        completed: true
    ctx.body = successXML

  setHome: (ctx) ->
    client = ctx.state.client
    home = ctx.request.body
    await db.CLIENT.findOneAndUpdate
      _id: client._id
    ,
      $set:
        home: home
    ctx.body = status: true

  chargeRed: (ctx) ->
    { money } = ctx.request.body
    openid = ctx.state.openid
    client = ctx.state.client
    order =
      await db.ORDER.insert
        client: client
        openid: openid
        total_fee: Math.round money * 100

    paybody =
      body: "红包充值" + money + "元"
      out_trade_no: order._id.toString()
      total_fee: order.total_fee
      spbill_create_ip: ctx.ip
      openid: openid
      notify_url: process.env.DOMAIN + "/client/red/charge/callback"
      trade_type: "JSAPI"

    console.log paybody
    ret = await util.promisify(utils.wepay.getBrandWCPayRequestParams.bind utils.wepay) paybody
    console.log ret
    ctx.body = ret

  chargeRedCallback: (ctx) ->
    { xml } = await utils.xml2js ctx.request.body
    order = await db.ORDER.findOne xml.out_trade_no
    console.log xml
    successXML = """<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>"""

    if order.completed
      return (ctx.body = successXML)

    await db.CLIENT.findOneAndUpdate
      _id: order.client._id
    ,
      $inc:
        red_fee: parseInt Math.floor (xml.total_fee * (100 - process.env.REDFEE || 2)) / 100
    await db.ORDER.findOneAndUpdate
      _id: monk.id xml.out_trade_no
    ,
      $set:
        completed: true
    ctx.body = successXML

  setPointRule: (ctx) ->
    client = ctx.state.client
    form = ctx.request.body
    rule = null
    if form._id
      rule =
        await db.POINT_RULE.findOneAndUpdate
          _id: monk.id form._id
        ,
          $set: _.pick form, [
            "invite_status"
            "invite_notify"
            "invite_point"
            "sign_status"
            "sign_start"
            "sign_end"
            "sign_keepdays"
            "sign_keeppoints"
            "sign_notify"
            "sign_point"
            "sign_word"
            "welcome"
            "replys"
          ]
    else
      form.clientid = client._id

      rule =
        await db.POINT_RULE.insert(
          _.pick form, [
            "roomid"
            "clientid"
            "invite_status"
            "invite_notify"
            "invite_point"
            "sign_status"
            "sign_notify"
            "sign_point"
            "sign_word"
            "welcome"
            "replys"
          ]
        )
    ctx.body = status: true, rule: rule

  getPointDraws: (ctx) ->
    draws = await db.POINT_DRAW.find { clientid: ctx.state.client._id, deleted: $ne: true }, sort: _id: -1
    ctx.body = status: true, draws: draws

  deletePointDraw: (ctx) ->
    { _id } = ctx.request.body
    await db.POINT_DRAW.findOneAndUpdate { _id: monk.id _id }, $set: deleted: true

    ctx.body = status: true
  setPointDraw: (ctx) ->
    client = ctx.state.client
    form = ctx.request.body
    # console.log form
    form.stock = parseInt form.stock
    form.opentime = new Date form.opentime
    draw = null
    if form._id
      draw = await db.POINT_DRAW.findOne monk.id form._id
      if draw.status != 0
        return ctx.body = status: false, msg: "活动已结束"
      draw =
        await db.POINT_DRAW.findOneAndUpdate
          _id: monk.id form._id
        ,
          $set: _.pick form, ["stock", "name", "desc", "point", "photo", "tips", "opentime"]
    else
      form.clientid = ctx.state.client._id
      form.status = 0
      draw =
        await db.POINT_DRAW.insert(
          _.pick form, ["clientid", "stock", "name", "desc", "point", "photo", "tips", "opentime", "status"]
        )
    ctx.body = status: true, draw: draw

  getPointItems: (ctx) ->
    items = await db.POINT_ITEM.find clientid: ctx.state.client._id
    if items.length is 0
      item =
        await db.POINT_ITEM.insert
          clientid: ctx.state.client._id
          type: "red"
          red_money: 0.3
          stock: 9999
          name: "0.3元红包"
          point: 3
          limit: "none"
          photo: "/img/icons/red.png"

      await db.CLIENT.findOneAndUpdate
        _id: ctx.state.client._id
      ,
        $set:
          home:
            label: "红包"
            value: item._id.toString()
    items = await db.POINT_ITEM.find clientid: ctx.state.client._id
    ctx.body = status: true, items: items

  getPointItem: (ctx) ->
    item = await db.POINT_ITEM.findOne ctx.params.itemid
    if item.type is "code"
      item.codes = item.codes.join "\n"
    ctx.body = status: true, item: item

  deletePointItem: (ctx) ->
    { _id } = ctx.request.body
    await db.POINT_ITEM.remove _id: monk.id _id
    ctx.body = status: true

  setPointItem: (ctx) ->
    client = ctx.state.client
    form = ctx.request.body
    # console.log form
    form.stock = parseInt form.stock
    if form.type == "code"
      form.codes = form.codes
        .split "\n"
        .filter (i) -> i
      form.stock = form.codes.length
    if form.type == "red"
      if form.red_money2
        form.name = "随机红包"
      else
        form.name = "#{form.red_money}元红包"
      form.photo = "/img/icons/red.png"
      if form.red_money < 0.3
        return (ctx.body = status: false, msg: "红包最少0.3元")
    item = null
    if form._id
      item =
        await db.POINT_ITEM.findOneAndUpdate
          _id: monk.id form._id
        ,
          $set: _.pick form, [
            "type"
            "stock"
            "name"
            "point"
            "limit"
            "photo"
            "red_money"
            "red_money2"
            "real_type"
            "real_desc"
            "getway"
            "qrcode"
            "code_desc"
            "codes"
            "priority"
            "disabled"
          ]
    else
      form.clientid = ctx.state.client._id
      item =
        await db.POINT_ITEM.insert(
          _.pick form, [
            "clientid"
            "type"
            "stock"
            "name"
            "point"
            "photo"
            "limit"
            "red_money"
            "real_type"
            "real_desc"
            "getway"
            "qrcode"
            "code_desc"
            "codes"
            "priority"
            "disabled"
          ]
        )

    ctx.body = status: true, item: item

  uploadImg: (ctx) ->
    { client } = ctx.state
    { file } = ctx.request.files
    try
      await fs.mkdir "./upload"
    catch e

    newPath = "/upload/#{client._id}-#{new Date().getTime()}.png"
    await fs.rename file.path, "." + newPath

    ctx.body = status: true, path: newPath

  saveUserPoint: (ctx) ->
    { contactid } = ctx.params
    clientid = ctx.state.client._id
    { inputPoint } = ctx.request.body
    inputPoint = parseInt inputPoint

    pd = await db.POINT_DATA.findOneAndUpdate
      clientid: clientid
      contactid: contactid
    ,
      $inc:
        point: inputPoint

    if isNaN(pd.point)
      await db.POINT_DATA.findOneAndUpdate
        clientid: clientid
        contactid: contactid
      ,
        $set:
          point: inputPoint

    await db.POINT_LOG.insert
      contactid: contactid
      clientid: clientid
      point: inputPoint
      desc: "管理员后台修改"

    ctx.body = status: true

  settingPage: (ctx) ->
    await ctx.render "client/setting"

  getSetting: (ctx) ->
    setting = await db.getMainSetting ctx.state.client._id
    ctx.body = status: true, setting: setting

  saveSetting: (ctx) ->
    { setting } = ctx.request.body
    await db.CLIENT.findOneAndUpdate
      _id: ctx.state.client._id
    ,
      $set: setting: _.pick setting, ["invitetips", "signtips"]
    ctx.body = status: true

  resetSetting: (ctx) ->
    await db.CLIENT.findOneAndUpdate { _id: ctx.state.client._id }, $unset: setting: 1
    ctx.body = status: true

  sendFormPage: (ctx) ->
    { sendid } = ctx.params
    await ctx.render "client/sendForm", sendid: if sendid then sendid else ""

  sendDetailPage: (ctx) ->
    sendid = ctx.request.params
    send = await db.SEND.findOne _id: monk.id sendid
    await ctx.render "client/send", send: send

  sendListPage: (ctx) ->
    clientid = ctx.state.client._id
    sends =
      await db.SEND.find
        clientid: clientid
      ,
        fields: ["rooms", "name", "mode", "weekdays", "daytime", "days", "month"]
    for send in sends
      send.topics = []
      for roomid in send.rooms
        room = await db.ROOM.findOne id: roomid, inviterid: ctx.state.client.contactid
        if room
          send.topics.push room.topic
    await ctx.render "client/sendList", sends: sends

  getMessagesPage: (ctx) ->
    clientid = ctx.state.client._id
    await ctx.render "client/messages", clientid: clientid

  getSend: (ctx) ->
    { sendid } = ctx.params
    send = await db.SEND.findOne _id: monk.id sendid
    ctx.body = status: true, send: send

  saveSend: (ctx) ->
    { send } = ctx.request.body
    send.daytime = send.daytime.replace /：/g, ":"
    if send._id
      send =
        await db.SEND.findOneAndUpdate
          _id: monk.id send._id
        ,
          $set: _.pick send, ["messages", "rooms", "name", "mode", "weekdays", "daytime", "days", "month"]
    else
      send.clientid = ctx.state.client._id
      inserted =
        await db.SEND.insert(
          _.pick send, ["messages", "rooms", "name", "mode", "weekdays", "daytime", "days", "month", "clientid"]
        )
      send = await db.SEND.findOne _id: inserted._id
    ctx.body = status: true, send: send

  doSend: (ctx) ->
    { sendid } = ctx.params
    await utils.send.doSend sendid
    ctx.body = status: true

  removeSend: (ctx) ->
    { sendid } = ctx.params
    await db.SEND.remove _id: monk.id sendid
    ctx.body = status: true

  getMessages: (ctx) ->

    selector = { clientid: ctx.state.client._id }
    if ctx.query.nextid
      selector._id = {'$lt':monk.id(ctx.query.nextid)}
    messages = await db.MESSAGE.find selector, sort: { _id: -1 }, limit: 30
    ctx.body = status: true, messages: messages

  getMsgList: (ctx)->
    selector = { clientid: ctx.state.client._id }
    if ctx.query.nextid
      selector._id = '$lt':monk.id(ctx.query.nextid)
    msgs = await db.MESSAGE.find selector, sort: { _id: -1 }, limit: 30, fields: ['_id', 'type']
    ctx.body = status: true, msgs: msgs

  getMsgDetail : (ctx)->
    msg = await db.MESSAGE.findOne {_id: monk.id ctx.params.msgid}
    ctx.body = status: true, msg: msg

  deleteMessage: (ctx) ->
    msg = ctx.request.body
    await db.MESSAGE.remove _id: monk.id msg._id
    ctx.body = status: true

  removeMessages: (ctx) ->
    { ids } = ctx.request.body
    for id in ids
      await db.MESSAGE.remove _id: monk.id id

    ctx.body = status: true
